實作 Redux(四):共享結構的物件提高性能


Posted by YongChenSu on 2020-12-14

log 加爆查看 function 效率問題

  1. 第一次渲染:印出前三個。
  2. 第一次 store.dispatch:印出中間三個。
  3. 第二次 store.dispatch:印出最後三個

發現不用 renderContenet



以下的優化方法可行嗎?

1. 先判斷若傳入相同的數據就不渲染

function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 没有傳入,所以加了默認參數 oldAppState = {}
  if (newAppState === oldAppState) return
  console.log('render app...')
  renderTitle(newAppState.title, oldAppState.title)
  renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle) {
  if (newTitle === oldTitle) return
  console.log('render title')
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = newTitle.text
  titleDOM.style.color = newTitle.color
}

function renderContent (newContent, oldContent) {
  if (newContent === oldContent) return
  console.log('render content')
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = newContent.text
  contentDOM.style.color = newContent.color
}

2. 再將舊的狀態儲存起來

const store = createStore(appState, stateChanger)
let oldState = store.getState() // 儲存舊的 state
store.subscribe(() => {
  const newState = store.getState() // 數據可能變化,獲取新的 state
  renderApp(newState, oldState) // 把新舊的 state 傳進去渲染
  oldState = newState // 渲染完成後,把新的 newState 變成舊的 oldState,等待下一次重新渲染
}) // 監聽數據變化

結果:這樣的方法行不通,因為改到相同的記憶體位置

查看 stateChanger 函式,發現是「修改相同記憶體位置上的東西」。

function stateChanger (state, action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      state.title.text = action.text
      break
    case 'UPDATE_TITLE_COLOR':
      state.title.color = action.color
      break
    default:
      break
  }
}

以上改動的如同以下的程式碼

修改相同的記憶體位置的東西

let appState = {
  title: {
    text: 'React.js 小書',
    color: 'red',
  },
  content: {
    text: 'React.js 小書內容',
    color: 'blue'
  }
}
const oldState = appState
appState.title.text = '《React.js 小书》'
oldState !== appState // false

了解一下 ES6 解構

const obj = {a: 1, b: 2}
const obj2 = { ...obj } // => { a: 1, b: 2 }
const obj3 = { ...obj, b: 3, c: 4} // => { a: 1, b: 2, c: 3 }

obj、obj2、obj3 的記憶體位置不同

改變記憶體位置

let newAppState = {
  ...appState, // 複製 appState 裡面的內容
  title: { // 用一個新的物件覆蓋原來 title 屬性
    ...appState.title,
    text: '新的 React.js 小書' // 覆蓋 text 屬性
  }
}

content 指的是原本的物件 (舊的記憶體位置),title 則不是

color 也是一樣

console.log('1. ', newAppState !== newAppState1) // ture
console.log('2. ', appState.title !== newAppState.title) // ture
console.log('3. ', appState.content !== appState.content) // false

優化性能

function stateChanger (state, action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'UPDATE_TITLE_COLOR':
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state // 沒有修改,回傳原來的狀態
  }
}

修改一下 createStore

每次呼叫 stateChanger(state, action) 後覆蓋原來的物件。

function createStore (state, stateChanger) {
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = stateChanger(state, action) // 覆盖原对象
    listeners.forEach((listener) => listener())
  }
  return { getState, dispatch, subscribe }
}

只有第一次印出 render content



本節完整程式碼

const appState = {
  title: {
    text: 'React.js 小書',
    color: 'red',
  },
  content: {
    text: 'React.js 小書內容',
    color: 'blue'
  }
}

let newAppState = {
  ...appState, // 複製 appState 裡面的內容
  title: { // 用一個新的物件覆蓋原來 title 屬性
    ...appState.title, // 複製原來 title 裡面的內容
    text: '新的 React.js 小書' // 覆蓋 text 屬性
  }
}

let newAppState1 = {
  ...newAppState, // 複製 newAppState 裡面的內容
  title: { // 用一個新的物件覆蓋原來 title 屬性
    ...newAppState.title, // 複製原來 title 裡面的內容
    text: '新的 React.js 小書' // 覆蓋 color 屬性
  }
}

console.log('1. ', newAppState !== newAppState1) // ture
console.log('2. ', appState.title !== newAppState.title) // ture
console.log('3. ', appState.content !== appState.content) // false

// 新增渲染函數
function renderApp (newAppState, oldAppState = {}) { // 防止 oldAppState 没有傳入,所以加了默認參數 oldAppState = {}
  if (newAppState === oldAppState) return
  console.log('render app...')
  renderTitle(newAppState.title, oldAppState.title)
  renderContent(newAppState.content, oldAppState.content)
}
function renderTitle (newTitle, oldTitle) {
  if (newTitle === oldTitle) return
  console.log('render title')
  const titleDOM = document.getElementById('title')
  titleDOM.innerHTML = newTitle.text
  titleDOM.style.color = newTitle.color
}

function renderContent (newContent, oldContent) {
  if (newContent === oldContent) return
  console.log('render content')
  const contentDOM = document.getElementById('content')
  contentDOM.innerHTML = newContent.text
  contentDOM.style.color = newContent.color
}

// createStore
function createStore (state, stateChanger) {
  const listeners = []
  const subscribe = (listener) => listeners.push(listener)
  const getState = () => state
  const dispatch = (action) => {
    state = stateChanger(state, action)
    listeners.forEach((listener) => listener())
  }
  return { getState, dispatch, subscribe }
}

function stateChanger (state, action) {
  switch (action.type) {
    case 'UPDATE_TITLE_TEXT':
      return {
        ...state,
        title: {
          ...state.title,
          text: action.text
        }
      }
    case 'UPDATE_TITLE_COLOR':
      return {
        ...state,
        title: {
          ...state.title,
          color: action.color
        }
      }
    default:
      return state // 沒有修改,回傳原來的狀態
  }
}

const store = createStore(appState, stateChanger)
let oldState = store.getState() // 儲存舊的 state
store.subscribe(() => {
  const newState = store.getState() // 數據可能變化,獲取新的 state
  renderApp(newState, oldState) // 把新舊的 state 傳進去渲染
  oldState = newState // 渲染完成後,把新的 newState 變成舊的 oldState,等待下一次重新渲染
}) // 監聽數據變化

renderApp(store.getState()) // first render
store.dispatch({type: 'UPDATE_TITLE_TEXT', text: 'React 小書'}) // 修改內容
store.dispatch({type: 'UPDATE_TITLE_COLOR', color: 'blue'}) // 修改標題顏色
// renderApp(store.getState()) // redner again


總結

  1. 在渲染函式中新增判斷式
  2. stateChanger 函式中複製相同內容但改變記憶體位置。
  3. createStore 中將舊的狀態 state 變成新的狀態 state。

藉由判斷是否改變記憶體、是否傳入相同的數據,決定要不要重新渲染。

不必擔心新增記憶體位置會有性能、內存問題,因為成本非常低。而且最多保存兩個物件引用 oldStatenewState,其餘的會被回收掉。

參考資源


#程式導師實驗計畫第四期 #前端 #React #react book #Redux #共享結構的物件







Related Posts

易於維護的寫法 補

易於維護的寫法 補

reverse engineer 1.1

reverse engineer 1.1

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?

What Type of Laser Engraving Machine Should be Used for Stainless Steel Engraving?


Comments